package com.cardone.generator;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import lombok.Cleanup;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
import org.springframework.jdbc.support.DatabaseMetaDataCallback;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.cardone.generator.mapper.FieldMapper;
import com.cardone.generator.mapper.PoMapper;
import com.cardone.generator.template.RunTemplate;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Maps;

/**
 * PO对象映射工厂
 *
 * @author yaohaitao
 */
@Getter
@Setter
@Accessors(chain = true)
@Slf4j
public class PoMapperFactory {
	private String defaultFieldType;

	private JdbcTemplate jdbcTemplate;

	private String productName;

	private RunTemplate runTemplate;

	private String schemaPattern;

	private Map<Integer, String> typeMap;

	/**
	 * 查询实体
	 *
	 * @param dbmd
	 *          DatabaseMetaData
	 * @param tableName
	 *          表名
	 * @return 实体
	 * @throws Exception
	 *           异常
	 */
	private PoMapper findPoMapper(final DatabaseMetaData dbmd, final String tableName) throws Exception {
		PoMapper poMapper = null;

		@Cleanup
		final ResultSet rs = dbmd.getTables(null, this.schemaPattern, tableName, null);

		final RowMapperResultSetExtractor<PoMapper> rowMapperResultSetExtractor = new RowMapperResultSetExtractor<PoMapper>(BeanPropertyRowMapper.newInstance(PoMapper.class), 0);

		final List<PoMapper> poMapperList = rowMapperResultSetExtractor.extractData(rs);

		Assert.notEmpty(poMapperList, "没有对应的表：" + tableName);

		poMapper = poMapperList.get(0);

		final String message = "载入:" + tableName;

		PoMapperFactory.log.info(message);

		this.initPoMapperRemarks(tableName, poMapper);

		this.initPoMapperFields(dbmd, tableName, poMapper);

		return poMapper;
	}

	/**
	 * 初始化
	 */
	public void init() {
		Assert.notNull(this.defaultFieldType);

		Assert.notEmpty(this.typeMap);

		Assert.notNull(this.runTemplate);
	}

	/**
	 * 初始化实体字段
	 *
	 * @param dbmd
	 *          DatabaseMetaData
	 * @param tableName
	 *          表名
	 * @param poMapper
	 *          实体对象
	 * @throws SQLException
	 *           异常
	 */
	private void initPoMapperFields(final DatabaseMetaData dbmd, final String tableName, final PoMapper poMapper) throws SQLException {
		Map<String, FieldMapper> fieldMapperMap = Maps.newTreeMap();

		List<FieldMapper> fieldMapperList;

		@Cleanup
		final ResultSet rs = dbmd.getColumns(null, this.schemaPattern, tableName, null);

		final RowMapperResultSetExtractor<FieldMapper> rowMapperResultSetExtractor = new RowMapperResultSetExtractor<FieldMapper>(BeanPropertyRowMapper.newInstance(FieldMapper.class), 0);

		fieldMapperList = rowMapperResultSetExtractor.extractData(rs);

		for (final FieldMapper fieldMapper : fieldMapperList) {
			final String code = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, fieldMapper.getColumnName());

			final String namePascalCase = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, fieldMapper.getColumnName());

			fieldMapper.setCode(code);
			fieldMapper.setName(code);
			fieldMapper.setNamePascalCase(namePascalCase);

			if (fieldMapperMap == null) {
				fieldMapperMap = Maps.newTreeMap();
			}

			String typeCode = this.typeMap.get(fieldMapper.getDataType());

			typeCode = StringUtils.defaultIfBlank(typeCode, this.defaultFieldType);

			fieldMapper.setTypeCode(typeCode);

			fieldMapperMap.put(code, fieldMapper);
		}

		if ("Oracle".equals(this.productName)) {
			final String sql = "SELECT COMMENTS FROM USER_COL_COMMENTS WHERE TABLE_NAME= ? AND COLUMN_NAME = ?";

			for (final FieldMapper fieldMapper : fieldMapperMap.values()) {
				final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[] { tableName, fieldMapper.getColumnName() }, String.class);

				fieldMapper.setRemarks(remarks);
			}
		} else if ("MySQL".equals(this.productName)) {
			final String sql = "SELECT IF(T.COLUMN_COMMENT = '' OR T.COLUMN_COMMENT IS NULL, T.COLUMN_NAME, T.COLUMN_COMMENT) FROM INFORMATION_SCHEMA.COLUMNS T WHERE T.TABLE_SCHEMA = ? AND T.TABLE_NAME = ? AND T.COLUMN_NAME = ?";

			for (final FieldMapper fieldMapper : fieldMapperMap.values()) {
				final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[] { this.schemaPattern, tableName, fieldMapper.getColumnName() }, String.class);

				fieldMapper.setRemarks(remarks);
			}
		}

		poMapper.setFieldMapperMap(fieldMapperMap);

		this.initPoMapperPrimaryKeys(dbmd, tableName, fieldMapperMap);
	}

	/**
	 * 初始化实体属性
	 *
	 * @param dbmd
	 *          DatabaseMetaData
	 * @throws MetaDataAccessException
	 *           异常
	 * @throws SQLException
	 *           异常
	 */
	private void initPoMapperMap(final DatabaseMetaData dbmd) throws MetaDataAccessException, SQLException {
		final List<PoMapper> poMapperList = this.runTemplate.findListPoMapper();

		if (CollectionUtils.isEmpty(poMapperList)) {
			return;
		}

		for (final PoMapper poMapper : poMapperList) {
			final String code = poMapper.getCode();

			PoMapper dbPoMapper = null;

			try {
				dbPoMapper = this.findPoMapper(dbmd, poMapper.getTableName());
			} catch (final Exception e) {
				PoMapperFactory.log.error(e.getMessage(), e);
			}

			if (dbPoMapper == null) {
				continue;
			}

			BeanUtils.copyProperties(dbPoMapper, poMapper);

			poMapper.setCode(code);
		}
	}

	/**
	 * 初始化实体主键
	 *
	 * @param dbmd
	 *          DatabaseMetaData
	 * @param tableName
	 *          表名
	 * @param fieldMapperMap
	 *          字段映射
	 * @throws SQLException
	 *           异常
	 */
	private void initPoMapperPrimaryKeys(final DatabaseMetaData dbmd, final String tableName, final Map<String, FieldMapper> fieldMapperMap) throws SQLException {
		List<FieldMapper> fieldMapperList;

		@Cleanup
		final ResultSet rs = dbmd.getPrimaryKeys(null, this.schemaPattern, tableName);

		final RowMapperResultSetExtractor<FieldMapper> rowMapperResultSetExtractor = new RowMapperResultSetExtractor<FieldMapper>(BeanPropertyRowMapper.newInstance(FieldMapper.class), 0);

		fieldMapperList = rowMapperResultSetExtractor.extractData(rs);

		if (CollectionUtils.isEmpty(fieldMapperList)) {
			return;
		}

		for (final FieldMapper itemFieldMapper : fieldMapperList) {
			final String code = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, itemFieldMapper.getColumnName());

			final FieldMapper fieldMapper = fieldMapperMap.get(code);

			fieldMapper.setKeySeq(itemFieldMapper.getKeySeq());
			fieldMapper.setPkName(itemFieldMapper.getPkName());
		}
	}

	/**
	 * 初始化实体说明
	 *
	 * @param tableName
	 *          表名
	 * @param poMapper
	 *          实体对象
	 * @throws MetaDataAccessException
	 *           异常
	 */
	private void initPoMapperRemarks(final String tableName, final PoMapper poMapper) throws MetaDataAccessException {
		this.productName = JdbcUtils.extractDatabaseMetaData(this.jdbcTemplate.getDataSource(), "getDatabaseProductName").toString();

		this.productName = JdbcUtils.commonDatabaseName(this.productName);

		if ("Oracle".equals(this.productName)) {
			final String sql = "SELECT NVL(COMMENTS, TABLE_NAME) FROM USER_TAB_COMMENTS WHERE TABLE_NAME= ?";

			try {
				final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[] { tableName }, String.class);

				poMapper.setRemarks(remarks);
			} catch (final Exception e) {
				PoMapperFactory.log.error(e.getMessage(), e);
			}
		} else if ("MySQL".equals(this.productName)) {
			final String sql = "select IF(T.TABLE_COMMENT = '' OR T.TABLE_COMMENT IS NULL, T.table_name, T.TABLE_COMMENT) from INFORMATION_SCHEMA.tables t where t.table_schema = ? and t.table_name = ?";

			try {
				final String remarks = this.jdbcTemplate.queryForObject(sql, new Object[] { this.schemaPattern, tableName }, String.class);

				poMapper.setRemarks(remarks);
			} catch (final Exception e) {
				PoMapperFactory.log.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 执行
	 *
	 * @throws Exception
	 *           异常
	 */
	public void run() throws Exception {
		JdbcUtils.extractDatabaseMetaData(this.jdbcTemplate.getDataSource(), new DatabaseMetaDataCallback() {
			@Override
			public Object processMetaData(final DatabaseMetaData dbmd) throws SQLException, MetaDataAccessException {
				PoMapperFactory.this.initPoMapperMap(dbmd);

				return null;
			}
		});

		this.runTemplate.run();
	}
}
